package com.xinggq.pay.service.pay.channel.yse.utils;

import static cn.hutool.crypto.KeyUtil.KEY_TYPE_PKCS12;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.eptok.yspay.opensdkjava.util.YsOnlineSignUtils;
import com.xinggq.pay.api.exception.PayException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.PrivateKey;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 银盛支付工具类
 * 提供签名生成、HTTP请求等功能
 *
 * @author qth
 * @since 2023/8/4 11:09
 */
@Slf4j
public final class YseUtil {


  /** 证书密码 */
  private static final String YSE_PASSWORD = "zhongxin@123";

  /** 私钥文件路径 */
  private static final String PRIVATE_KEY_FILE = "cert/nmgxxt.pfx";

  /** HTTP连接超时时间(毫秒) */
  private static final int CONNECTION_TIMEOUT = 30000;

  /** HTTP读取超时时间(毫秒) */
  private static final int READ_TIMEOUT = 60000;

  /** 默认字符编码 */
  private static final String DEFAULT_CHARSET = "UTF-8";

  private YseUtil() {
    // 工具类不允许实例化
  }

  /**
   * 生成签名
   *
   * @param obj 待签名对象
   * @param charSet 字符编码
   * @return 签名字符串
   */
  public static String generateSign(Object obj, String charSet) {
    if (obj == null) {
      throw new IllegalArgumentException("待签名对象不能为空");
    }
    if (StrUtil.isBlank(charSet)) {
      charSet = DEFAULT_CHARSET;
    }

    try {
      // 处理原数据
      String data = mapToSortedString(BeanUtil.beanToMap(obj, true, true));
      log.debug("待签名数据: {}", data);

      // 获取私钥并签名
      PrivateKey privateKey = loadPrivateKey();
      Sign sign = SecureUtil.sign(SignAlgorithm.SHA1withRSA).setPrivateKey(privateKey);
      String signature = Base64.encode(sign.sign(data.getBytes(StandardCharsets.UTF_8)));

      return URLEncoder.encode(signature, charSet);
    } catch (Exception e) {
      log.error("生成签名失败, 对象: {}", obj, e);
      throw new PayException("签名生成失败: " + e.getMessage());
    }
  }

  /**
   * 生成银行支付签名(不进行URL编码)
   *
   * @return 签名字符串
   */
  public static String generateSignWithOutEncode(String json) {
    if (StringUtils.isBlank(json)) {
      throw new IllegalArgumentException("银行支付参数不能为空");
    }

    try {
      Map<String, String> map = JSONUtil.toBean(json, new TypeReference<>() {
      }, false);

      String keyFilePath = getPrivateKeyFilePath();
      return YsOnlineSignUtils.sign(map, YSE_PASSWORD, keyFilePath);
    } catch (Exception e) {
      log.error("银行支付签名生成失败, 参数: {}", json, e);
      throw new PayException("银行支付签名生成失败: " + e.getMessage());
    }
  }

  /**
   * 将Map按键排序后转换为字符串
   *
   * @param map 待转换的Map
   * @return 排序后的字符串
   */
  public static String mapToSortedString(Map<String, Object> map) {
    if (map == null || map.isEmpty()) {
      return "";
    }

    SortedMap<String, Object> sortedMap = new TreeMap<>(map);
    StringBuilder sb = new StringBuilder();

    for (Map.Entry<String, Object> entry : sortedMap.entrySet()) {
      String value = String.valueOf(entry.getValue());
      if (StrUtil.isNotBlank(value) && !"null".equals(value)) {
        if (!sb.isEmpty()) {
          sb.append('&');
        }
        sb.append(entry.getKey()).append('=').append(value);
      }
    }

    return sb.toString();
  }

  /**
   * 发送HTTP POST请求
   *
   * @param url 请求URL
   * @param param 请求参数
   * @return 响应结果
   */
  public static String httpPostSpecial(String url, String param) {
    if (StrUtil.isBlank(url)) {
      throw new IllegalArgumentException("请求URL不能为空");
    }
    if (StrUtil.isBlank(param)) {
      throw new IllegalArgumentException("请求参数不能为空");
    }

    log.debug("发送POST请求, URL: {}, 参数: {}", url, param);

    try {
      HttpResponse response = HttpRequest.post(url)
          .header("Accept", "*/*")
          .header("Connection", "Keep-Alive")
          .header("User-Agent", "Pay-Service/1.0")
          .header("Accept-Charset", DEFAULT_CHARSET)
          .header("Content-Type", "application/x-www-form-urlencoded;charset=" + DEFAULT_CHARSET)
          .timeout(CONNECTION_TIMEOUT)
          .body(param)
          .execute();

      try (response) {  // 使用 try-with-resources 自动关闭 HttpResponse
        if (!response.isOk()) {
          log.error("HTTP请求失败, 状态码: {}, 响应: {}", response.getStatus(), response.body());
          throw new PayException("HTTP请求失败，状态码: " + response.getStatus());
        }

        String result = response.body();
        log.debug("HTTP请求成功, 响应: {}", result);
        return result;
      }

    } catch (Exception e) {
      log.error("发送POST请求异常, URL: {}, 参数: {}", url, param, e);
      throw new PayException("网络请求失败: " + e.getMessage());
    }
  }

  /**
   * 加载私钥
   *
   * @return 私钥对象
   */
  private static PrivateKey loadPrivateKey() {
    try {
      KeyStore store = SecureUtil.readKeyStore(KEY_TYPE_PKCS12,
          ResourceUtil.getStream(PRIVATE_KEY_FILE),
          YSE_PASSWORD.toCharArray());
      return SecureUtil.generatePrivateKey(store,
          store.aliases().nextElement(),
          YSE_PASSWORD.toCharArray());
    } catch (Exception e) {
      log.error("加载私钥失败", e);
      throw new PayException("私钥加载失败: " + e.getMessage());
    }
  }

  /**
   * 获取私钥文件路径
   * 解决Spring Boot打包后jar包内资源文件无法获取绝对路径的问题
   *
   * @return 私钥文件路径
   */
  private static String getPrivateKeyFilePath() {
    try {
      // 首先检查应用目录下是否存在证书文件
      File privateKeyFile = new File(AppUtils.getAppHome() + File.separator + PRIVATE_KEY_FILE);
      if (privateKeyFile.exists()) {
        return privateKeyFile.getPath();
      }

      // 从classpath读取资源文件并复制到临时文件
      ClassPathResource resource = new ClassPathResource(PRIVATE_KEY_FILE);
      if (!resource.exists()) {
        throw new PayException("证书文件不存在: " + PRIVATE_KEY_FILE);
      }

      // 创建临时文件
      File tempFile = Files.createTempFile("nmgxxt", ".pfx").toFile();
      tempFile.deleteOnExit(); // 程序退出时删除临时文件

      // 将classpath资源复制到临时文件
      try (InputStream inputStream = resource.getInputStream();
           FileOutputStream outputStream = new FileOutputStream(tempFile)) {
        
        byte[] buffer = new byte[8192];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
          outputStream.write(buffer, 0, bytesRead);
        }
        outputStream.flush();
      }

      log.debug("证书文件已复制到临时文件: {}", tempFile.getAbsolutePath());
      return tempFile.getAbsolutePath();
      
    } catch (IOException e) {
      log.error("复制证书文件到临时目录失败", e);
      throw new PayException("证书文件处理失败: " + e.getMessage());
    } catch (Exception e) {
      log.error("获取私钥文件路径失败", e);
      throw new PayException("私钥文件路径获取失败: " + e.getMessage());
    }
  }
}
